#ifndef VM_HPP__
#define VM_HPP__

#include <boost/smart_ptr.hpp>
#include <map>
#include <vector>
#include <string>
#include <stack>
#include <stdexcept>
#include <sstream>
#include <ostream>

#include "value.hpp"
#include "instr.hpp"
#include "vm_func.hpp"

using namespace std;
using namespace boost;

namespace BMD
{
  class Actor;

  /**
   * function information on the stack
   */
  struct function_call_data
  {
    function_call_data(string _func, vector<Value>::iterator beg, 
                     vector<Value>::iterator end, bool _local);

    string func_name;
    vector<Value> args;
    bool local_call;
  };
  ostream& operator<< (ostream& o, const function_call_data& fcd);

  /**
   * Virtual Machine class. 
   * Contains the logic for executing instructions from its parent actor.
   */
  class VM
  {
  public:
    Actor* get_actor() const { return parent_actor; }

    /** stack pushing */
    void push();
    void push(Value vr);

    /** stack popping */
    Value pop();
    void pop(unsigned int num_entries);
    void pop_unsafe(unsigned int num_entries);

    /* stack element access */
    Value get_stack_element(unsigned int stack_loc) const;
    void get_stack_elements(unsigned int num_elements, 
                            vector<Value>::iterator result) const;
    unsigned int get_stack_elements_unsafe(unsigned int num_elements,
                                   vector<Value>::iterator result) const;

    void set_stack_element(unsigned int stack_loc, Value vr);
    
    //! fills a vector with the stack arguments
    void get_call_arguments(vector<Value>& args) const;
    
    //! returns the number of arguments on the stack
    int get_num_call_arguments() const;

    //! reset running parameters
    void reset();

    //! execute instruction
    void execute(InstructionRef i);
    
    //! execute instructions, starting at the current ip, given certain break
    //! conditions 
    void run(int flags = 0);

    //! call a function from the active actor
    Value call_function(string func_name, const vector<Value>&, bool debug = false);

    Value get_accumulator() const;
    void set_accumulator(Value v);

    /** instruction pointer access */
    int get_instruction_pointer() const { return ip; }
    void set_instruction_pointer(int i) { ip = i; }

    //! go up a stack frame
    void pop_stack_frame();

    //! locate the position of the stack frame
    int get_stack_frame_location();

    //! push a stack frame
    void push_stack_frame(int ret_addr);

    //! halt current program execution
    void stop() { running = false; }
    
    //! check program execution
    bool is_running() const { return running; }

    //! check the validity of the instruction pointer
    bool is_valid_ip() const;

    bool has_function(string& s) const 
    { return vm_function_map.find(s) != vm_function_map.end(); }    
    VMFunction get_function(string& s) const;
    void add_function(string name, VMFunction func);

    //! add a func
    void push_function_call(string name, int num_args = -1, bool local = true);
    void pop_function_call();

    void print_call_stack(ostream& o = cout);
    void get_call_stack(vector<function_call_data>& fc_stack, bool local);

    /* debugging functions */    
    void stack_dump(ostream& o);
    void set_debug_output(bool b) { debug_output = b; }

    static const int ALL_ARGUMENTS = -1;

    static const int STOP_AT_BREAKPOINTS = 1;
    static const int STOP_AT_RETURNS = 2;
    static const int STOP_AT_ASSERTIONS = 4;

  protected:
    static const unsigned int RETURN_ADDRESS_SIZE;


  private:
    friend class Actor;

    //! signals the compiler to be in "running" mode without actually executing
    //! any instructions
    void start() { running = true; }
    
    // virtual machines can only be created by
    // their parent actors
    VM(Actor* _parent_actor);
    VM(const VM& vm);
    ~VM() { }

    //! transform an internal stack location to an external stack location.
    int int_to_ext_stack(int loc) const { return stack.size() - 1 - loc; }
    int ext_to_int_stack(int loc) const { return stack.size() - 1 - loc; }

    /** Pointer to the actor where the VM gets its information.
        The VM is *not* responsible for deleting its parent actor. */
    Actor* parent_actor; 
    
    //! stack for local variables, function parameters, and call frames
    vector< Value > stack;

    //! stack for function call details
    vector< function_call_data > call_stack;
    
    //! registered vm functions
    map<string, VMFunction> vm_function_map;
     
   //! instruction pointer
    int ip;

    //! accumulator (register)
    Value acc;

    /** stores the position of current stack frame
     * the position is in internal representation, *not*
     * in terms of the stack position */
    int curr_sfp;
    
    //! true if an actor is currently executing
    bool running;

    //! controls whether or not stack information is printed while running
    bool debug_output;
  };  
  
}

#endif
